import keras
import numpy as np
import colorsys
import pickle
import os
from nets.efficientdet import Efficientdet
from keras import backend as K
from keras.layers import Input
from PIL import Image, ImageFont, ImageDraw
from utils.utils import BBoxUtility, letterbox_image, efficientdet_correct_boxes
from utils.anchors import get_anchors

image_sizes = [512, 640, 768, 896, 1024, 1280, 1408]


def preprocess_input(image):
    image /= 255
    mean = [0.485, 0.456, 0.406]
    std = [0.229, 0.224, 0.225]
    image -= mean
    image /= std
    return image


# --------------------------------------------#
#   使用自己训练好的模型预测需要修改3个参数
#   model_path和classes_path和phi都需要修改！
# --------------------------------------------#
class EfficientDet(object):
    _defaults = {
        "model_path": 'logs/ep061-loss0.117-val_loss0.156.h5',
        "classes_path": 'model_data/voc_classes.txt',
        "phi": 0,
        "confidence": 0.4,
    }

    @classmethod
    def get_defaults(cls, n):
        if n in cls._defaults:
            return cls._defaults[n]
        else:
            return "Unrecognized attribute name '" + n + "'"

    # ---------------------------------------------------#
    #   初始化efficientdet
    # ---------------------------------------------------#
    def __init__(self, **kwargs):
        self.__dict__.update(self._defaults)
        self.class_names = self._get_class()
        self.model_image_size = [image_sizes[self.phi], image_sizes[self.phi], 3]
        self.sess = K.get_session()
        self.generate()
        self.bbox_util = BBoxUtility(self.num_classes)
        self.prior = self._get_prior()

    # ---------------------------------------------------#
    #   获得所有的分类
    # ---------------------------------------------------#
    def _get_class(self):
        classes_path = os.path.expanduser(self.classes_path)
        with open(classes_path) as f:
            class_names = f.readlines()
        class_names = [c.strip() for c in class_names]
        return class_names

    def _get_prior(self):
        data = get_anchors(image_sizes[self.phi])
        return data

    # ---------------------------------------------------#
    #   获得所有的分类
    # ---------------------------------------------------#
    def generate(self):
        model_path = os.path.expanduser(self.model_path)
        assert model_path.endswith('.h5'), 'Keras model or weights must be a .h5 file.'

        # 计算总的种类
        self.num_classes = len(self.class_names)

        # 载入模型
        self.Efficientdet = Efficientdet(self.phi, self.num_classes)
        self.Efficientdet.load_weights(self.model_path, by_name=True, skip_mismatch=True)

        # self.Efficientdet.summary()
        print('{} model, anchors, and classes loaded.'.format(model_path))

        # 画框设置不同的颜色
        hsv_tuples = [(x / len(self.class_names), 1., 1.)
                      for x in range(len(self.class_names))]
        self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
        self.colors = list(
            map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
                self.colors))

        # ---------------------------------------------------#
        #   根据IOU过滤候选框
        # ---------------------------------------------------#

    def compute_iou(self, box1, box2, wh=False):
        if wh == False:
            xmin1, ymin1, xmax1, ymax1 = box1
            xmin2, ymin2, xmax2, ymax2 = box2
        else:
            xmin1, ymin1 = int(box1[0] - box1[2] / 2.0), int(box1[1] - box1[3] / 2.0)
            xmax1, ymax1 = int(box1[0] + box1[2] / 2.0), int(box1[1] + box1[3] / 2.0)
            xmin2, ymin2 = int(box2[0] - box2[2] / 2.0), int(box2[1] - box2[3] / 2.0)
            xmax2, ymax2 = int(box2[0] + box2[2] / 2.0), int(box2[1] + box2[3] / 2.0)

        ## 获取矩形框交集对应的左上角和右下角的坐标（intersection）
        xx1 = np.max([xmin1, xmin2])
        yy1 = np.max([ymin1, ymin2])
        xx2 = np.min([xmax1, xmax2])
        yy2 = np.min([ymax1, ymax2])

        ## 计算两个矩形框面积
        area1 = (xmax1 - xmin1) * (ymax1 - ymin1)
        area2 = (xmax2 - xmin2) * (ymax2 - ymin2)

        inter_area = (np.max([0, xx2 - xx1])) * (np.max([0, yy2 - yy1]))  # 计算交集面积
        iou = inter_area / (area1 + area2 - inter_area + 1e-6)  # 计算交并比

        return iou

    def selectBox_ByIOU(self, indeixlist, boxlist, scorelist):
        templist = []
        length = len(boxlist)
        inlist = range(length)
        for i in range(0, length - 1):
            for j in range(i + 1, length):
               # print(len(boxlist),indeixlist[j],len(indeixlist))
                iou = self.compute_iou(boxlist[i], boxlist[j])
                # print(iou,i,j)
                # print('******************************')
                if (iou > 0.7):
                    if scorelist[i] > scorelist[j]:
                        templist.append(j)
                    else:
                        templist.append(i)
        templist = list(set(templist))
        templist.sort(reverse=True)
        # print(templist,boxlist,scorelist)
        for i in templist:
            boxlist = np.delete(boxlist, i, axis=0)

        return [i for i in inlist if i not in templist], boxlist

    # ---------------------------------------------------#
    #   检测图片
    # ---------------------------------------------------#
    def detect_image(self, image):
        image_shape = np.array(np.shape(image)[0:2])

        crop_img = letterbox_image(image, [self.model_image_size[0], self.model_image_size[1]])
        photo = np.array(crop_img, dtype=np.float32)

        # 图片预处理，归一化
        photo = np.reshape(preprocess_input(photo),
                           [1, self.model_image_size[0], self.model_image_size[1], self.model_image_size[2]])

        preds = self.Efficientdet.predict(photo)

        # 将预测结果进行解码
        results = self.bbox_util.detection_out(preds, self.prior, confidence_threshold=self.confidence)
        if len(results[0]) <= 0:
            return image
        results = np.array(results)

        # ---------------------------------------------------#
        #   NMS，阈值
        # ---------------------------------------------------#
        det_label = results[0][:, 0]
        det_conf = results[0][:, 1]
        # print(det_label)  #检测结果中的类别
        # print('=============det_conf==========================================')
        # print(det_conf)  #检测结果中的得分
        # print('=============box========================================')
        det_xmin, det_ymin, det_xmax, det_ymax = results[0][:, 2], results[0][:, 3], results[0][:, 4], results[0][:, 5]
        # print(det_xmin, det_ymin, det_xmax, det_ymax)  # 检测结果中的框：[left_1,left_2][top_1,top_2][right_1,right_2][bottom_1,bottpm_2]

        # print('===============大于预设置信度的框在result中的下标和预设阈值============================')
        top_indices = [i for i, conf in enumerate(det_conf) if conf >= 0.5]
        # print(len(top_indices),top_indices)
        # print(top_indices, self.confidence)  # 大于预设置信度的框在result中的下标
        # print('===============大于预设置信度的预测置信度========================')
        top_label = det_label[top_indices]
        top_conf = det_conf[top_indices]
        #print(top_conf)
        #print(top_conf)
        # print(top_conf)  # 大于预设置信度的预测置信度
        # print('==============大于预设置信度的类以list形式返回==========================')
        # top_label_indices = det_label[top_indices].tolist()
        # print(top_label_indices)  # 大于预设置信度的类以list形式返回
        # print('==============大于预设置信度的box======================')
        top_xmin, top_ymin, top_xmax, top_ymax = np.expand_dims(det_xmin[top_indices], -1), np.expand_dims(
            det_ymin[top_indices], -1), np.expand_dims(det_xmax[top_indices], -1), np.expand_dims(det_ymax[top_indices],
                                                                                                  -1)
        # print(top_xmin, top_ymin, top_xmax, top_ymax)  # 大于预设置信度的box
        # print('==================box====================================')

        # 去掉灰条
        boxes = efficientdet_correct_boxes(top_ymin, top_xmin, top_ymax, top_xmax,
                                           np.array([self.model_image_size[0], self.model_image_size[1]]), image_shape)

        # print(boxes,type(boxes))  # box
        # print(top_indices,top_label_indices)


        # print('************************筛选后的*************************************')
        # ---------------------------------------------------#
        #   NMS，IOU
        # ---------------------------------------------------#
        top_indices, boxes = self.selectBox_ByIOU(top_indices, boxes, top_conf)
        # print(top_indices,boxes,type(boxes))
        # print('===============大于预设置信度的预测置信度========================')
        top_conf = top_conf[top_indices]
        # print(top_conf)  # 大于预设置信度的预测置信度
        # print('==============大于预设置信度的类以list形式返回==========================')
        top_label_indices = top_label[top_indices].tolist()
        #print(top_conf)
        # print(top_label_indices)  # 大于预设置信度的类以list形式返回
        # print('=============================================================')

        font = ImageFont.truetype(font='model_data/simhei.ttf',
                                  size=np.floor(3e-2 * np.shape(image)[1] + 0.5).astype('int32'))

        thickness = (np.shape(image)[0] + np.shape(image)[1]) // self.model_image_size[0]
        bboxes_n = []

        for i, c in enumerate(top_label_indices):
            predicted_class = self.class_names[int(c) - 1]
            score = top_conf[i]

            top, left, bottom, right = boxes[i]
            bboxes_n.append({"x1": round(left, 2), "y1": round(top, 2),
                             "x2": round(right, 2), "y2": round(bottom, 2),
                             "confidence": round(score, 3), "label": predicted_class})
            # print('===================================')
            # print(top, left, bottom, right,predicted_class)
            # print('===================================')
            top = top - 5
            left = left - 5
            bottom = bottom + 5
            right = right + 5

            top = max(0, np.floor(top + 0.5).astype('int32'))
            left = max(0, np.floor(left + 0.5).astype('int32'))
            bottom = min(np.shape(image)[0], np.floor(bottom + 0.5).astype('int32'))
            right = min(np.shape(image)[1], np.floor(right + 0.5).astype('int32'))

            # 画框框
            label = '{} {:.2f}'.format(predicted_class, score)
            draw = ImageDraw.Draw(image)
            label_size = draw.textsize(label, font)
            label = label.encode('utf-8')
            # print(label)

            if top - label_size[1] >= 0:
                text_origin = np.array([left, top - label_size[1]])
            else:
                text_origin = np.array([left, top + 1])

            for i in range(thickness):
                draw.rectangle(
                    [left + i, top + i, right - i, bottom - i],
                    outline=self.colors[int(c) - 1])
            draw.rectangle(
                [tuple(text_origin), tuple(text_origin + label_size)],
                fill=self.colors[int(c) - 1])
            draw.text(text_origin, str(label, 'UTF-8'), fill=(0, 0, 0), font=font)
            del draw
        return image, bboxes_n

    def close_session(self):
        self.sess.close()
